package model;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;


import exceptions.AttrBestaatInAttrGroepException;
import exceptions.AttribuutBestaatException;
import exceptions.AttribuutBestaatNietException;
import exceptions.AttribuutGroepNaamBestaatInAttribuutGroepenException;
import exceptions.AttribuutLijstLeegException;
import exceptions.AttribuutNaamKomtNietVoorInPrimaireSleutelException;
import exceptions.BuitenlandseSleutelVerwijstNaarNietBestaandRecordException;
import exceptions.DatabankBestaatException;
import exceptions.DatabankBestaatNietException;
import exceptions.DatabankNaamException;
import exceptions.DatabankNietVerbondenException;
import exceptions.FilterIsLeegException;
import exceptions.GeenAttributenException;
import exceptions.GeenKolomNamenException;
import exceptions.GeenDatabankConnectie;
import exceptions.GeenPrimaireSleutelException;
import exceptions.GeenTabelException;
import exceptions.GroepNaamException;
import exceptions.LijstenNietEvenLangException;
import exceptions.NulOnbekendException;
import exceptions.PrimaireSleutelAttribuutNamenException;
import exceptions.PrimaireSleutelDuplicaatException;
import exceptions.PrimaireSleutelLijstLeegException;
import exceptions.PrimaireSleutelNaamException;
import exceptions.RecordWaardeIsLeegException;
import exceptions.RecordWaardeMagNietLeegZijn;
import exceptions.RecordWaardeNietGevondenException;
import exceptions.ReferentieBestaatException;
import exceptions.ReferentieBestaatNietException;
import exceptions.SoortIncorrectException;
import exceptions.SoortOnbekendException;
import exceptions.TabelBestaatException;
import exceptions.TabelBestaatNietException;
import exceptions.TabelNaamException;

public class MyRDBMS implements RDBMS{
	private static HashMap<String, Databank> databanken;
	private Databank connected;

	public static void main(String[] args) {
		new MyRDBMS();
	}

	public MyRDBMS() {
		databanken = new HashMap<String, Databank>();
		connected = null;
		try {
//			init();//TODO weer activeren voor het echte gebruik.
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void init() throws DatabankBestaatException, SQLException, TabelBestaatException, TabelBestaatNietException, AttribuutBestaatException, ReferentieBestaatException, ReferentieBestaatNietException, DatabankNaamException, SoortOnbekendException, NulOnbekendException {
		String url = null;
		String user = null;
		String pass = null;

		Connection connection = DriverManager.getConnection(url, user, pass);
		
		//Databanken aanmaken
		ResultSet result = connection.createStatement().executeQuery("SELECT * FROM 'Databank'");

		while(result.next()) {
			createDatabank(result.getString(1));
		}

		PreparedStatement prTabel = connection.prepareStatement("SELECT * FROM 'Tabel' WHERE 'DatabankNaam' = ?");
		PreparedStatement prAttribuut = connection.prepareStatement("SELECT * FROM 'Attribuut' WHERE 'DatabankNaam' = ? AND 'TabelNaam' = ?");
		PreparedStatement prReferentie = connection.prepareStatement("SELECT * FROM 'Referentie' WHERE 'DatabankNaam' = ? AND 'TabelNaam' = ?");
		PreparedStatement prPrimaireSleutel = connection.prepareStatement("SELECT * FROM 'PrimaireSleutel' WHERE 'DatabankNaam' = ? AND 'TabelNaam' = ?");
		PreparedStatement prAttribuutGroep = connection.prepareStatement("SELECT * FROM 'AttribuutGroep' WHERE 'DatabankNaam' = ? AND 'TabelNaam' = ?");

		//Tabellen toevoegen aan databanken
		for(Databank databank : databanken.values()) {
			prTabel.setString(1, databank.getDatabankNaam());
			result = prTabel.executeQuery();

			while(result.next()) {
				databank.addTabel(new Tabel(result.getString(1), result.getString(2)));
			}

			//Attributen toevoegen aan tabellen
			for(String tabelNaam: databank.getTabellenHashMapKeys()) {
				Tabel tabel = databank.getTabel(tabelNaam);

				prAttribuut.setString(1, tabel.getDatabankNaam());
				prAttribuut.setString(2, tabel.getNaam());

				result = prAttribuut.executeQuery();

				while(result.next()) {
					tabel.addAttribuut(new Attribuut(result.getString(1), result.getString(2), result.getString(3), result.getString(4), result.getString(5)));
				}

				prPrimaireSleutel.setString(1, tabel.getDatabankNaam());
				prPrimaireSleutel.setString(2, tabel.getNaam());

				result = prPrimaireSleutel.executeQuery();

				while(result.next()) {
					tabel.setPrimaireSleutel(new PrimaireSleutel(result.getString(1), tabel.getDatabankNaam(), tabel));
				}

				prReferentie.setString(1, tabel.getDatabankNaam());
				prReferentie.setString(2, tabel.getNaam());

				prReferentie.executeQuery();

				while(result.next()) {
					tabel.addReferentie(new Referentie(result.getString(1), tabel.getPrimaireSleutel())); //De getPrimaireSleutel kan gedaan worden omdat er toch maar 1 primaire sleutel per tabel is.
				}

				for(String referentieNaam : tabel.getReferentiesHashMapKeys()) {
					Referentie referentie = tabel.getReferentie(referentieNaam);

					prAttribuutGroep.setString(1, tabel.getDatabankNaam());
					prAttribuutGroep.setString(2, tabel.getNaam());

					result = prAttribuutGroep.executeQuery();

					while(result.next()) {
						referentie.setVanGroep(new AttribuutGroep(result.getString(1), tabel.getDatabankNaam(), tabel));						
					}
				}
			}
		}
	}

	//DDL
	public void createDatabank(String databankNaam) throws DatabankBestaatException, DatabankNaamException {
		if(!databankNaam.equals("")){
			if(!databanken.containsKey(databankNaam)) {
				databanken.put(databankNaam, new Databank(databankNaam));
			} else {
				throw new DatabankBestaatException("Databank " + databankNaam + " bestaat al!");
			}
		} else {
			throw new DatabankNaamException("Databanknaam mag niet leeg zijn!");
		}
	}

	public void connectDatabank(String databankNaam) throws DatabankBestaatNietException, DatabankNaamException {
		if(!databankNaam.equals("")){
			if(databanken.containsKey(databankNaam)) {
				connected = databanken.get(databankNaam);
			} else {
				throw new DatabankBestaatNietException("Databank " + databankNaam + " bestaat niet.");
			}
		} else {
			throw new DatabankNaamException("Databanknaam mag niet leeg zijn!");
		}
	}

	public void disconnectDatabank() {
		connected = null;
	}

	public void createTabel(String tabelNaam, ArrayList<ArrayList<String>> attributen, ArrayList<String> primaireSleutel, String primaireSleutelNaam) throws AttribuutBestaatException, AttribuutBestaatNietException, AttrBestaatInAttrGroepException, GeenDatabankConnectie, TabelBestaatException, TabelNaamException, AttribuutLijstLeegException, PrimaireSleutelLijstLeegException, PrimaireSleutelNaamException, SoortOnbekendException, NulOnbekendException, TabelBestaatNietException, PrimaireSleutelDuplicaatException {
		if(connected == null) {
			throw new GeenDatabankConnectie("Er is geen databank connectie");
		}
		if(tabelNaam == null || tabelNaam.equals("")) {
			throw new TabelNaamException("Tabelnaam is leeg");
		}
		if(attributen == null || attributen.size() == 0 || attributen.get(0).size() == 0) {
			throw new AttribuutLijstLeegException("Attributenlijst is leeg");
		}
		if(primaireSleutel == null || primaireSleutel.size() == 0) {
			throw new PrimaireSleutelLijstLeegException("PrimaireSleutellijst is leeg");
		}
		if(primaireSleutelNaam == null || primaireSleutelNaam.equals("")) {
			throw new PrimaireSleutelNaamException("PrimaireSleutelNaam is leeg");
		}
		String[] tabellen = connected.getTabellenHashMapKeys();
		for(String str: tabellen) {
			Tabel temp = connected.getTabel(str);
			if(temp.getPrimaireSleutel().getGroepNaam().equals(primaireSleutelNaam)) {
				throw new PrimaireSleutelDuplicaatException("Primaire sleutel " + primaireSleutelNaam + " bestaat al");
			}
		}

		Tabel tabel = new Tabel(connected.getDatabankNaam(), tabelNaam);
		for(int i = 0; i < attributen.size(); i++) {
			tabel.addAttribuut(new Attribuut(connected.getDatabankNaam(), tabel.getNaam(), attributen.get(i).get(0), attributen.get(i).get(1), attributen.get(i).get(2)));
		}
		PrimaireSleutel pSleutel = new PrimaireSleutel(primaireSleutelNaam, connected.getDatabankNaam(), tabel);
		for(String attribuut : primaireSleutel) {
			pSleutel.addAttribuut(tabel.getAttribuut(attribuut));
		}
		tabel.setPrimaireSleutel(pSleutel);
		connected.addTabel(tabel);
	}
	
//	public void createTabel(String tabelNaam, ArrayList<ArrayList<String>> attributen, ArrayList<String> primaireSleutel, String primaireSleutelNaam, String groepNaam, PrimaireSleutel primKey, Tabel naartoeTabel, ArrayList<String> attrNaarToe) throws AttribuutBestaatException, AttribuutBestaatNietException, AttrBestaatInAttrGroepException, GeenDatabankConnectie, TabelBestaatException, TabelNaamException, AttribuutLijstLeegException, PrimaireSleutelLijstLeegException, PrimaireSleutelNaamException, SoortOnbekendException, NulOnbekendException, ReferentieBestaatException, GroepNaamException, GeenTabelException, GeenPrimaireSleutelException, GeenAttributenException, AttribuutGroepNaamBestaatInAttribuutGroepenException, TabelBestaatNietException, PrimaireSleutelDuplicaatException {
//		createTabel(tabelNaam, attributen, primaireSleutel, primaireSleutelNaam);
//		
//		toevoegenBuitenlandseSleutel(groepNaam, primKey, naartoeTabel, attrNaarToe);
//	}
	
//	public void toevoegenBuitenlandseSleutel(String groepNaam, PrimaireSleutel primaireSleutel, Tabel naartoeTabel, ArrayList<String> attributenNaartoe) throws AttrBestaatInAttrGroepException, AttribuutBestaatNietException, ReferentieBestaatException, GroepNaamException, GeenTabelException, GeenPrimaireSleutelException, GeenAttributenException, AttribuutGroepNaamBestaatInAttribuutGroepenException {
//		if(groepNaam == null || groepNaam.equals("")) {
//			throw new GroepNaamException("Groepnaam is leeg");
//		}
//		if(attribuutGroepen.containsKey(groepNaam)) {
//			throw new AttribuutGroepNaamBestaatInAttribuutGroepenException("Attribuutgroep naaam " + "groepNaam" + " bestaat al in Attribuutgroepen");
//		}
//		if(primaireSleutel == null) {
//			throw new GeenPrimaireSleutelException("Er is geen primaire sleutel");
//		}
//		if(attributenNaartoe == null || attributenNaartoe.size() == 0) {
//			throw new GeenAttributenException("Er zijn geen attributen meegegeven");
//		}
//		if(naartoeTabel == null) {
//			throw new GeenTabelException("Er is geen tabel");
//		}
//		
//			Referentie ref = new Referentie(groepNaam, primaireSleutel);
//			AttribuutGroep attrGroep = new AttribuutGroep(groepNaam, connected.getDatabankNaam(), naartoeTabel);
//			for(String attrNamen : attributenNaartoe) {
//				attrGroep.addAttribuut(naartoeTabel.getAttribuut(attrNamen));
//			}
//			attribuutGroepen.put(groepNaam, attrGroep);
//			
//			ref.setVanafGroep(attrGroep);
//			primaireSleutel.getTabel().addReferentie(ref);
//	}
	
	public void toevoegenBuitenlandseSleutel2(String groepNaam, Tabel vanTabel, Tabel naarTabel, String[] attributenVanaf) throws AttribuutBestaatNietException, AttrBestaatInAttrGroepException, ReferentieBestaatException, GroepNaamException, GeenTabelException, GeenAttributenException {
		if(groepNaam == null || groepNaam.equals("")) {
			throw new GroepNaamException("Groepnaam is leeg");
		}
		if(vanTabel == null) {
			throw new GeenTabelException("Er is geen vanaftabel");
		}
		if(naarTabel == null) {
			throw new GeenTabelException("Er is geen naartabel");
		}
		if(attributenVanaf == null || attributenVanaf.length == 0) {
			throw new GeenAttributenException("Er zijn geen attributen meegegeven");
		}
		
		AttribuutGroep attribuutGroep = new AttribuutGroep(groepNaam, connected.getDatabankNaam(), vanTabel);
		for(int i=0; i<attributenVanaf.length; i++) {
			Attribuut attribuut = vanTabel.getAttribuut(attributenVanaf[i]);
			attribuutGroep.addAttribuut(attribuut);
		}
	
		PrimaireSleutel primaireSleutel = naarTabel.getPrimaireSleutel();
		
		Referentie referentie = new Referentie(groepNaam, primaireSleutel);
		referentie.setVanGroep(attribuutGroep);
		vanTabel.addReferentie(referentie);
	}

	public void veranderTabel(String tabelNaam, String colomNaam, Datatypen datatype) {
		//TODO!
	}
	public void veranderTabel(String tabelNaam, String colomNaam) {
		//TODO Goed kijken naar het verwijderen van primairy key. Dit kun je het beste doen wanneer de tabel leeg is. anders error gooien.
	}

	public void verwijderTabel(String tabelNaam) throws GeenDatabankConnectie, TabelBestaatNietException, ReferentieBestaatNietException {
		if(connected == null) {
			throw new GeenDatabankConnectie("Er is geen databank connectie");
		}
		//checken of tabel wel bestaat in de connected db
		Tabel teVerwijderenTabel = connected.getTabel(tabelNaam);//Wanneer tabel niet word gevonden word er een TabelBestaatNietException gegooid.

		
		//alle records verwijderen.. goed kijken naar de key's ?! De primairy key pakken en door de andere tabellen lopen die deze primary key gebruiken en verwijderen.
		PrimaireSleutel priKey = teVerwijderenTabel.getPrimaireSleutel();
		String[] referentieKeys = teVerwijderenTabel.getReferentiesHashMapKeys();
		
		ArrayList<String> refToRemove = new ArrayList<String>();//TODO Misschien is het maar 1 string... moet nog even worden nagekeken.
		for(String refKey : referentieKeys) {
			Referentie ref = teVerwijderenTabel.getReferentie(refKey);
			if(ref.getNaarPrimaireSleutel().getGroepNaam().equals(priKey.getGroepNaam())) {
				refToRemove.add(refKey);
			}
		}
		for(String refs : refToRemove) {
			teVerwijderenTabel.deleteReferentie(refs);
		}
		
		//Tabel verwijderen
		connected.verwijderTabel(tabelNaam);//Gooit een TabelBestaatNietException als tabel niet bestaat in databank(extra check, eigenlijk niet nodig maar stel dat die methode van ergens anders word aangeroepen dan zal die exception alsnog worden gegooit).
	}

	public void verwijderDatabank(String databankNaam) throws DatabankBestaatNietException {
		if(databanken.containsKey(databankNaam)) {
			if(connected != null && connected.getDatabankNaam().equals(databankNaam)) {
				connected = null;//Er kan dan niet meer worden verbonden met de verwijderde database.
			}
			databanken.remove(databankNaam);
		} else {
			throw new DatabankBestaatNietException("De databank: " + databankNaam + " bestaat niet.");
		}
	}
	
	public String toonTabellen() throws GeenDatabankConnectie {//Representatie van de tabellen in een String, wel even nakijken of dit handig is.
		if(connected == null) {
			throw new GeenDatabankConnectie("Er is geen databank connectie");
		}
		return connected.printTabellen();
	}

	//DML
	public void toevoegen(String tabelNaam, String[] namen, String[] waardes) throws GeenDatabankConnectie, LijstenNietEvenLangException, TabelBestaatNietException, AttribuutBestaatNietException, SoortOnbekendException, SoortIncorrectException, RecordWaardeIsLeegException, RecordWaardeMagNietLeegZijn, PrimaireSleutelAttribuutNamenException, AttribuutNaamKomtNietVoorInPrimaireSleutelException, PrimaireSleutelDuplicaatException, BuitenlandseSleutelVerwijstNaarNietBestaandRecordException {
		//Check connection
		if(connected == null) {
			throw new GeenDatabankConnectie("Er is geen databank connectie");
		}
		
		//Check of tabel bestaat, throws TabelBestaatNietException
		Tabel tabel = connected.getTabel(tabelNaam);
		
		//Check of aantal keys gelijk is aan aantal waardes
		if(namen.length != waardes.length) {
			throw new LijstenNietEvenLangException("Lijst van namen en lijst van waardes bevatten niet evenveel elementen");
		}	
		
		//Voeg record toe aan tabel, throws AttribuutBestaatNietException
		tabel.addRecord(namen, waardes);
	}

	public void verwijderen(String tabelNaam, String filterKey, String filterValue) throws GeenDatabankConnectie, AttribuutBestaatNietException, FilterIsLeegException, TabelBestaatNietException, BuitenlandseSleutelVerwijstNaarNietBestaandRecordException, ReferentieBestaatNietException {
		//Check connection
		if(connected == null) {
			throw new GeenDatabankConnectie("Er is geen databank connectie");
		}
		
		//Check of tabel bestaat, throws TabelBestaatNietException
		Tabel tabel = connected.getTabel(tabelNaam);
		tabel.deleteRecords(filterKey, filterValue);
	}
	
	public void update(String tabelNaam, String[] namen, String[] waardes, String filterKey, String filterValue) throws GeenDatabankConnectie, TabelBestaatNietException, AttribuutBestaatNietException, SoortIncorrectException, RecordWaardeMagNietLeegZijn, SoortOnbekendException, RecordWaardeIsLeegException, FilterIsLeegException, LijstenNietEvenLangException, PrimaireSleutelAttribuutNamenException, PrimaireSleutelDuplicaatException, AttribuutNaamKomtNietVoorInPrimaireSleutelException, ReferentieBestaatNietException, BuitenlandseSleutelVerwijstNaarNietBestaandRecordException {
		//Check connection
		if(connected == null) {
			throw new GeenDatabankConnectie("Er is geen databank connectie");
		}
		
		//Check of tabel bestaat, throws TabelBestaatNietException
		Tabel tabel = connected.getTabel(tabelNaam);
		tabel.updateRecords(namen, waardes, filterKey, filterValue);
	}

	public String selecteren(ArrayList<String> kolomNamen, String tabelNaam, String filterKey, String filterValue) throws GeenKolomNamenException, TabelBestaatNietException, GeenDatabankConnectie, RecordWaardeNietGevondenException, AttribuutBestaatNietException, FilterIsLeegException {
		if(connected == null) {
			throw new GeenDatabankConnectie("Er is geen databank connectie");
		}
		Tabel selectTabel = connected.getTabel(tabelNaam);//Gooit een exception wanneer tabel niet bestaat/tabelNaam leeg of null is.
		if(filterKey != null && !filterKey.equals("")) {
			if(filterValue == null || filterValue.equals("")) {
				throw new FilterIsLeegException("Filterwaarde is leeg.");
			}
			selectTabel.getAttribuut(filterKey);//Om te checken of het attribuut van het filter bestaat.
		}
		if(kolomNamen == null || kolomNamen.size() == 0) {
			throw new GeenKolomNamenException("Er is/zijn geen kolomna(a)m(en) meegegeven");
		}
		String output = "";
		if(kolomNamen.get(0).equals("*")) {
			String[] attribuutNamen = selectTabel.getAttribuutNamen();
			for(int i=0; i<attribuutNamen.length; ++i) {
				output += attribuutNamen[i] + " | ";
			}
			output += "\n";
			
			for(Record record : selectTabel.getRecords(filterKey, filterValue)) {
				output += record.toString() + "\n";
			}
		} else {
			for(String colomNaam : kolomNamen) {
				output += colomNaam + " | ";
			}
			output += "\n";
			
			for(Record record : selectTabel.getRecords(filterKey, filterValue)) {
				output += record.toString(kolomNamen) + "\n";
			}
			
		}
		return output;	
	}

	
	
	//Methoden nodig om te kunnen testen
	public HashMap<String, Databank> getDatabanken() {
		return databanken;
	}
	
	public Databank getConnected() throws DatabankNietVerbondenException {
		if(this.connected == null) {
			throw new DatabankNietVerbondenException("Er is nog geen verbinding gemaakt met een databank, roep eerst connect() aan.");
		}
		return connected;
	}
	
	public static Databank getDatabank(String databankNaam) {
		return databanken.get(databankNaam);
	}
}
